/* ====================================================================
 * 
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.tools.ant.taskdefs.optional.scm; 
 
import org.apache.tools.ant.*;
import java.io.*;
import java.util.*;
import com.starbase.starteam.*;
import com.starbase.util.Platform;

/**
 * Checks out files from a specific StarTeam server, project, view, and
 * folder.
 * <BR><BR>
 * This program logs in to a StarTeam server and opens up the specified
 * project and view.  Then, it searches through that view for the given
 * folder (or, if you prefer, it uses the root folder).  Beginning with
 * that folder and optionally continuing recursivesly, AntStarTeamCheckOut
 * compares each file with your include and exclude filters and checks it
 * out only if appropriate.
 * <BR><BR>
 * Checked out files go to a directory you specify under the subfolder
 * named for the default StarTeam path to the view.  That is, if you
 * entered /home/cpovirk/work as the target folder, your project was named
 * "OurProject," the given view was named "TestView," and that view is
 * stored by default at "C:\projects\Test," your files would be checked
 * out to /home/cpovirk/work/Test."  I avoided using the project name in
 * the path because you may want to keep several versions of the same
 * project on your computer, and I didn't want to use the view name, as
 * there may be many "Test" or "Version 1.0" views, for example.  This
 * system's success, of course, depends on what you set the default path
 * to in StarTeam.
 * <BR><BR>
 * You can set AntStarTeamCheckOut to verbose or quiet mode.  Also, it has
 * a safeguard against overwriting the files on your computer:  If the
 * target directory you specify already exists, the program will throw a
 * BuildException.  To override the exception, set <CODE>force</CODE> to
 * true.
 * <BR><BR>
 * <B>This program makes use of functions from the StarTeam API.  As a result
 * AntStarTeamCheckOut is available only to licensed users of StarTeam and
 * requires the StarTeam SDK to function.  You must have
 * <CODE>starteam-sdk.jar</CODE> in your classpath to run this program.
 * For more information about the StarTeam API and how to license it, see
 * the link below.</B>
 *
 * @author <A HREF="mailto:chris.povirk@paytec.com">Chris Povirk</A>
 * @author <A HREF="mailto:jc.mann@paytec.com">JC Mann</A>
 * @author <A HREF="mailto:jeff.gettle@paytec.com">Jeff Gettle</A>
 * @version 1.0
 * @see <A HREF="http://www.starbase.com/">StarBase Web Site</A>
 */
public class AntStarTeamCheckOut extends org.apache.tools.ant.Task 
{

	/**
	 * By default, <CODE>force</CODE> is set to "false" through this field.
	 * If you set <CODE>force</CODE> to "true," AntStarTeamCheckOut will
	 * overwrite files in the target directory. If the target directory does
	 * not exist, the <CODE>force</CODE> setting does nothing. Note that
	 * <CODE>DEFAULT_FORCESETTING</CODE> and <CODE>force</CODE> are strings,
	 * not boolean values. See the links below for more information.
	 * 
	 * @see #getForce()
	 * @see #getForceAsBoolean()
	 * @see #setForce(String force)
	 */
	static public final String DEFAULT_FORCESETTING = "false";

	/**
	 * This field is used in setting <CODE>verbose</CODE> to "false", the
	 * default. If <CODE>verbose</CODE> is true, AntStarTeamCheckOut will
	 * display file and directory names as it checks files out. The default
	 * setting displays only a total. Note that
	 * <CODE>DEFAULT_VERBOSESETTING</CODE> and <CODE>verbose</CODE> are
	 * strings, not boolean values. See the links below for more
	 * information.
	 * 
	 * @see #getVerbose()
	 * @see #getVerboseAsBoolean()
	 * @see #setVerbose(String verbose)
	 */
	static public final String DEFAULT_VERBOSESETTING = "false";

	/**
	 * <CODE>DEFAULT_RECURSIONSETTING</CODE> contains the normal setting --
	 * true -- for recursion.  Thus, if you do not
	 * <CODE>setRecursion("false")</CODE> somewhere in your program,
	 * AntStarTeamCheckOut will check files out from all subfolders as well
	 * as from the given folder.
	 * 
	 * @see #getRecursion()
	 * @see #setRecursion(String recursion)
	 */
	static public final String DEFAULT_RECURSIONSETTING = "true";

	/**
	 * This constant sets the filter to include all files. This default has
	 * the same result as <CODE>setIncludes("*")</CODE>.
	 * 
	 * @see #getIncludes()
	 * @see #setIncludes(String includes)
	 */
	static public final String DEFAULT_INCLUDESETTING = "*";

	/**
	 * This disables the exclude filter by default. In other words, no files
	 * are excluded. This setting is equivalent to
	 * <CODE>setExcludes(null)</CODE>.
	 * 
	 * @see #getExcludes()
	 * @see #setExcludes(String excludes)
	 */
	static public final String DEFAULT_EXCLUDESETTING = null;

	/**
	 * The default folder to search; the root folder.  Since
	 * AntStarTeamCheckOut searches subfolders, by default it processes an
	 * entire view.
	 * 
	 * @see #getFolderName()
	 * @see #setFolderName(String folderName)
	 */
	static public final String DEFAULT_FOLDERSETTING = null;


	/**
	 * This is used when formatting the output. The directory name is
	 * displayed only when it changes.
	 */
	private Folder prevFolder = null;

	/**
	 * This field keeps count of the number of files checked out.
	 */
	private int checkedOut;

	// Change these through their GET and SET methods.

	/**
	 * The name of the server you wish to connect to.
	 */
	private String serverName = null;

	/**
	 * The port on the server used for StarTeam.
	 */
	private String serverPort = null;

	/**
	 * The name of your project.
	 */
	private String projectName = null;

	/**
	 * The name of the folder you want to check out files from. All
	 * subfolders will be searched, as well.
	 */
	private String folderName = DEFAULT_FOLDERSETTING;

	/**
	 * The view that the files you want are in.
	 */
	private String viewName = null;

	/**
	 * Your username on the StarTeam server.
	 */
	private String username = null;

	/**
	 * Your StarTeam password.
	 */
	private String password = null;

	/**
	 * The path to the root folder you want to check out to. This is a local
	 * directory.
	 */
	private String targetFolder = null;

	/**
	 * If force set to true, AntStarTeamCheckOut will overwrite files in the
	 * target directory.
	 */
	private String force = DEFAULT_FORCESETTING;

	/**
	 * When verbose is true, the program will display all files and
	 * directories as they are checked out.
	 */
	private String verbose = DEFAULT_VERBOSESETTING;

	/**
	 * Set recursion to false to check out files in only the given folder
	 * and not in its subfolders.
	 */
	private String recursion = DEFAULT_RECURSIONSETTING;

	// These fields deal with includes and excludes

	/**
	 * All files that fit this pattern are checked out.
	 */
	private String includes = DEFAULT_INCLUDESETTING;

	/**
	 * All files fitting this pattern are ignored.
	 */
	private String excludes = DEFAULT_EXCLUDESETTING;

	/**
	 * The file delimitor on the user's system.
	 */
	private String delim = Platform.getFilePathDelim();

	/**
	 * Do the execution.
	 * 
	 * @exception BuildException
	 */
	public void execute() throws BuildException
	{

		// Check all of the properties that are required.
		if ( getServerName() == null )
		{
			project.log("ServerName must not be null.");
			return;
		}
		if ( getServerPort() == null )
		{
			project.log("ServerPort must not be null.");
			return;
		}
		if ( getProjectName() == null )
		{
			project.log("ProjectName must not be null.");
			return;
		}
		if ( getViewName() == null )
		{
			project.log("ViewName must not be null.");
			return;
		}
		if ( getUsername() == null )
		{
			project.log("Username must not be null.");
			return;
		}
		if ( getPassword() == null )
		{
			project.log("Password must not be null.");
			return;
		}
		if ( getTargetFolder() == null )
		{
			project.log("TargetFolder must not be null.");
			return;
		}

		// Because of the way I create the full target path, there must be NO slash at the end of targetFolder and folderName
		// However, if the slash or backslash is the only character, leave it alone
		if (!(getTargetFolder()==null))
		{
			if ((getTargetFolder().endsWith("/") || getTargetFolder().endsWith("\\")) && getTargetFolder().length() > 1)
			{
				setTargetFolder(getTargetFolder().substring(0, getTargetFolder().length() - 1));
			}
		}

		if (!(getFolderName()==null))
		{
			if ((getFolderName().endsWith("/") || getFolderName().endsWith("\\")) && getFolderName().length() > 1)
			{
				setFolderName(getFolderName().substring(0, getFolderName().length() - 1));
			}
		}

		// Check to see if the target directory exists.
		java.io.File dirExist = new java.io.File(getTargetFolder());
		if (dirExist.isDirectory() && !getForceAsBoolean())
		{
			project.log("Target directory exists. Set \"force\" to \"true\" to continue anyway.");
			return;
		}

		try
		{
			// Connect to the StarTeam server, and log on.
			Server s = getServer();

			// Search the items on this server.
			runServer(s);

			// Disconnect from the server.
			s.disconnect();

			// after you are all of the properties are ok, do your thing
			// with StarTeam.  If there are any kind of exceptions then
			// send the message to the project log.

			// Tell how many files were checked out.
			project.log(checkedOut + " files checked out.");
		}
		catch (Throwable e)
		{
			project.log("    " + e.getMessage());
		}
	}

	/**
	 * Creates and logs in to a StarTeam server.
	 * 
	 * @return A StarTeam server.
	 */
	protected Server getServer()
	{
		// Simplest constructor, uses default encryption algorithm and compression level.
		Server s = new Server(getServerName(), getServerPortAsInt());

		// Optional; logOn() connects if necessary.
		s.connect();

		// Logon using specified user name and password.
		s.logOn(getUsername(), getPassword());

		return s;
	}

	/**
	 * Searches for the specified project on the server.
	 * 
	 * @param s      A StarTeam server.
	 */
	protected void runServer(Server s)
	{

		com.starbase.starteam.Project[] projects = s.getProjects();
		for (int i = 0; i < projects.length; i++)
		{
			com.starbase.starteam.Project p = projects[i];

			if (p.getName().equals(getProjectName()))
			{
				if (getVerboseAsBoolean())
				{
					project.log("Found " + getProjectName() + delim);
				}
				runProject(s, p);
				break;
			}
		}
	}

	/**
	 * Searches for the given view in the project.
	 * 
	 * @param s      A StarTeam server.
	 * @param p      A valid project on the given server.
	 */
	protected void runProject(Server s, com.starbase.starteam.Project p)
	{
		View[] views = p.getViews();
		for (int i = 0; i < views.length; i++)
		{
			View v = views[i];
			if (v.getName().equals(getViewName()))
			{
				if (getVerboseAsBoolean())
				{
					project.log("Found " + getProjectName() + delim + getViewName() + delim);
				}
				runType(s, p, v, s.typeForName((String)s.getTypeNames().FILE));
				break;
			}
		}
	}

	/**
	 * Searches for folders in the given view.
	 * 
	 * @param s      A StarTeam server.
	 * @param p      A valid project on the server.
	 * @param v      A view name from the specified project.
	 * @param t      An item type which is currently always "file".
	 */
	protected void runType(Server s, com.starbase.starteam.Project p, View v, Type t)
	{

		// This is ugly; checking for the root folder.
		Folder f = v.getRootFolder();
		if (!(getFolderName()==null))
		{
			if (getFolderName().equals("\\") || getFolderName().equals("/"))
			{
				setFolderName(null);
			}
			else
			{
				f = StarTeamFinder.findFolder(v.getRootFolder(), getFolderName());
			}
		}

		if (getVerboseAsBoolean() && !(getFolderName()==null))
		{
			project.log("Found " + getProjectName() + delim + getViewName() + delim + getFolderName() + delim + "\n");
		}

		// For performance reasons, it is important to pre-fetch all the
		// properties we'll need for all the items we'll be searching.

		// We always display the ItemID (OBJECT_ID) and primary descriptor.
		int nProperties = 2;

		// We'll need this item type's primary descriptor.
		Property p1 = getPrimaryDescriptor(t);

		// Does this item type have a secondary descriptor?
		// If so, we'll need it.
		Property p2 = getSecondaryDescriptor(t);
		if (p2 != null)
		{
			nProperties++;
		}

		// Now, build an array of the property names.
		String[] strNames = new String[nProperties];
		int iProperty = 0;
		strNames[iProperty++] = s.getPropertyNames().OBJECT_ID;
		strNames[iProperty++] = p1.getName();
		if (p2 != null)
		{
			strNames[iProperty++] = p2.getName();
		}

		// Pre-fetch the item properties and cache them.
		f.populateNow(t.getName(), strNames, -1);

		// Now, search for items in the selected folder.
		runFolder(s, p, v, t, f);

		// Free up the memory used by the cached items.
		f.discardItems(t.getName(), -1);
	}

	/**
	 * Searches for files in the given folder.  This method is recursive and
	 * thus searches all subfolders.
	 * 
	 * @param s      A StarTeam server.
	 * @param p      A valid project on the server.
	 * @param v      A view name from the specified project.
	 * @param t      An item type which is currently always "file".
	 * @param f      The folder to search.
	 */
	protected void runFolder(Server s, com.starbase.starteam.Project p, View v, Type t, Folder f)
	{

		// Process all items in this folder.
		Item[] items = f.getItems(t.getName());
		for (int i = 0; i < items.length; i++)
		{
			runItem(s, p, v, t, f, items[i]);
		}

		// Process all subfolders recursively if recursion is on.
		if (getRecursionAsBoolean())
		{
			Folder[] subfolders = f.getSubFolders();
			for (int i = 0; i < subfolders.length; i++)
			{
				runFolder(s, p, v, t, subfolders[i]);
			}
		}
	}

	/**
	 * Check out one file if it matches the include filter but not the
	 * exclude filter.
	 * 
	 * @param s      A StarTeam server.
	 * @param p      A valid project on the server.
	 * @param v      A view name from the specified project.
	 * @param t      An item type which is currently always "file".
	 * @param f      The folder the file is localed in.
	 * @param item   The file to check out.
	 */
	protected void runItem(Server s, com.starbase.starteam.Project p, View v, Type t, Folder f, Item item)
	{

		// Get descriptors for this item type.
		Property p1 = getPrimaryDescriptor(t);
		Property p2 = getSecondaryDescriptor(t);

		// Time to filter...
		String pName = (String)item.get(p1.getName());
		boolean includeIt = false;
		boolean excludeIt = false;

		// See if it fits any includes.
		if (getIncludes()!=null)
		{
			StringTokenizer inStr = new StringTokenizer(getIncludes(), " ");
			while (inStr.hasMoreTokens())
			{
				if (match(inStr.nextToken(), pName))
				{
					includeIt = true;
				}
			}
		}

		// See if it fits any excludes.
		if (getExcludes()!=null)
		{
			StringTokenizer exStr = new StringTokenizer(getExcludes(), " ");
			while (exStr.hasMoreTokens())
			{
				if (match(exStr.nextToken(), pName))
				{
					excludeIt = true;
				}
			}
		}

		// Don't check it out if
		// (a) It fits no include filters
		// (b) It fits an exclude filter
		if (!includeIt | excludeIt)
		{
			return;
		}

		// VERBOSE MODE ONLY
		if (getVerboseAsBoolean())
		{
			// Show folder only if changed.
			boolean bShowHeader = true;
			if (f != prevFolder)
			{
				// We want to display the folder the same way you would
				// enter it on the command line ... so we remove the 
				// View name (which is also the name of the root folder,
				// and therefore shows up at the start of the path).
				String strFolder = f.getFolderHierarchy();
				int i = strFolder.indexOf(delim);
				if (i >= 0)
				{
					strFolder = strFolder.substring(i+1);
				}
				System.out.println("            Folder: \"" + strFolder + "\"");
				prevFolder = f;
			}
			else
				bShowHeader	= false;

			// If we displayed the project, view, item type, or folder,
			// then show the list of relevant item properties.
			if (bShowHeader)
			{
				System.out.print("                Item");
				System.out.print(",\t" + p1.getDisplayName());
				if (p2 != null)
				{
					System.out.print(",\t" + p2.getDisplayName());
				}
				System.out.println("");
			}

			// Finally, show the Item properties ...

			// Always show the ItemID.
			System.out.print("                " + item.getItemID());

			// Show the primary descriptor.
			// There should always be one.
			System.out.print(",\t" + formatForDisplay(p1, item.get(p1.getName())));

			// Show the secondary descriptor, if there is one.
			// Some item types have one, some don't.
			if (p2 != null)
			{
				System.out.print(",\t" + formatForDisplay(p2, item.get(p2.getName())));
			}

			// Show if the file is locked.
			int locker = item.getLocker();
			if (locker>-1)
			{
				System.out.println(",\tLocked by " + locker);
			}
			else
			{
				System.out.println(",\tNot locked");
			}
		}
		// END VERBOSE ONLY

		// Check it out; also ugly.

		// Change the item to be checked out to a StarTeam File.
		com.starbase.starteam.File remote = (com.starbase.starteam.File)item;

		// Create a variable dirName that contains the name of the StarTeam folder that is the root folder in this view.
		// Get the default path to the current view.
		String dirName = v.getDefaultPath();
		// Settle on "/" as the default path separator for this purpose only.
		dirName = dirName.replace('\\', '/');
                // Take the StarTeam folder name furthest down in the hierarchy.
                int endDirIndex = dirName.length();
                // If it ends with separator then strip it off
                if (dirName.endsWith("/"))
                {
                    // This should be the SunOS and Linux case
                    endDirIndex--;
                }
		dirName = dirName.substring(dirName.lastIndexOf("/", dirName.length() - 2) + 1, endDirIndex);
                
		// Replace the projectName in the file's absolute path to the viewName.
		// This eventually makes the target of a checkout operation equal to:
		// targetFolder + dirName + [subfolders] + itemName
		StringTokenizer pathTokenizer = new StringTokenizer(item.getParentFolder().getFolderHierarchy(), delim);
		String localName = delim;
		String currentToken = null;
		while (pathTokenizer.hasMoreTokens())
		{
			currentToken = pathTokenizer.nextToken();
			if (currentToken.equals(getProjectName()))
			{
				currentToken = dirName;
			}
			localName += currentToken + delim;
		}
		// Create a reference to the local target file using the format listed above.
		java.io.File local = new java.io.File(getTargetFolder() + localName + item.get(p1.getName()));
		try
		{
			remote.checkoutTo(local, Item.LockType.UNCHANGED, false, true, true);
		}
		catch (Throwable e)
		{
			project.log("    " + e.getMessage());
		}
		checkedOut++;
	}

	/**
	 * Get the primary descriptor of the given item type.
	 *  Returns null if there isn't one.
	 *  In practice, all item types have a primary descriptor.
	 * 
	 * @param t      An item type. At this point it will always be "file".
	 * @return The specified item's primary descriptor.
	 */
	protected Property getPrimaryDescriptor(Type t)
	{
		Property[] properties = t.getProperties();
		for (int i = 0; i < properties.length; i++)
		{
			Property p = properties[i];
			if (p.isPrimaryDescriptor())
			{
				return p;
			}
		}
		return null;
	}

	/**
	 * Get the secondary descriptor of the given item type.
	 * Returns null if there isn't one.
	 * 
	 * @param t      An item type. At this point it will always be "file".
	 * @return The specified item's secondary descriptor. There may not be
	 *         one for every file.
	 */
	protected Property getSecondaryDescriptor(Type t)
	{
		Property[] properties = t.getProperties();
		for (int i = 0; i < properties.length; i++)
		{
			Property p = properties[i];
			if (p.isDescriptor() && !p.isPrimaryDescriptor())
			{
				return p;
			}
		}
		return null;
	}

	/**
	 * Formats a property value for display to the user.
	 * 
	 * @param p      An item property to format.
	 * @param value
	 * @return A string containing the property, which is truncated to 35
	 *         characters for display.
	 */
	protected String formatForDisplay(Property p, Object value)
	{
		if (p.getTypeCode() == Property.Types.TEXT)
		{
			String str = value.toString();
			if (str.length() > 35)
			{
				str = str.substring(0, 32) + "...";
			}
			return "\"" + str + "\"";
		}
		else
		{
			if (p.getTypeCode() == Property.Types.ENUMERATED)
			{
				return "\"" + p.getEnumDisplayName(((Integer)value).intValue()) + "\"";
			}
			else
			{
				return value.toString();
			}
		}
	}

	// TORN STRAIGHT FROM ANT.DIRECTORYSCANNER

	/**
	 * <B>TORN STRAIGHT FROM ANT.DIRECTORYSCANNER</B>
	 * 
	 * Matches a string against a pattern. The pattern contains two special
	 * characters:<BR>
	 * '*' which means zero or more characters,<BR>
	 * '?' which means one and only one character.
	 * 
	 * @param pattern the (non-null) pattern to match against
	 * @param str     the (non-null) string that must be matched against the
	 *                pattern
	 * @return <code>true</code> when the string matches against the
	 *         pattern, <code>false</code> otherwise.
	 */
	private static boolean match(String pattern, String str)
	{
		char[] patArr = pattern.toCharArray();
		char[] strArr = str.toCharArray();
		int patIdxStart = 0;
		int patIdxEnd   = patArr.length-1;
		int strIdxStart = 0;
		int strIdxEnd   = strArr.length-1;
		char ch;

		boolean containsStar = false;
		for (int i = 0; i < patArr.length; i++)
		{
			if (patArr[i] == '*')
			{
				containsStar = true;
				break;
			}
		}

		if (!containsStar)
		{
			// No '*'s, so we make a shortcut
			if (patIdxEnd != strIdxEnd)
			{
				return false;	// Pattern and string do not have the same size
			}
			for (int i = 0; i <= patIdxEnd; i++)
			{
				ch = patArr[i];
				if (ch != '?' && ch != strArr[i])
				{
					return false;	// Character mismatch
				}
			}
			return true; // String matches against pattern
		}

		if (patIdxEnd == 0)
		{
			return true; // Pattern contains only '*', which matches anything
		}

		// Process characters before first star
		while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd)
		{
			if (ch != '?' && ch != strArr[strIdxStart])
			{
				return false;
			}
			patIdxStart++;
			strIdxStart++;
		}
		if (strIdxStart > strIdxEnd)
		{
			// All characters in the string are used. Check if only '*'s are
			// left in the pattern. If so, we succeeded. Otherwise failure.
			for (int i = patIdxStart; i <= patIdxEnd; i++)
			{
				if (patArr[i] != '*')
				{
					return false;
				}
			}
			return true;
		}

		// Process characters after last star
		while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd)
		{
			if (ch != '?' && ch != strArr[strIdxEnd])
			{
				return false;
			}
			patIdxEnd--;
			strIdxEnd--;
		}
		if (strIdxStart > strIdxEnd)
		{
			// All characters in the string are used. Check if only '*'s are
			// left in the pattern. If so, we succeeded. Otherwise failure.
			for (int i = patIdxStart; i <= patIdxEnd; i++)
			{
				if (patArr[i] != '*')
				{
					return false;
				}
			}
			return true;
		}

		// process pattern between stars. padIdxStart and patIdxEnd point
		// always to a '*'.
		while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd)
		{
			int patIdxTmp = -1;
			for (int i = patIdxStart+1; i <= patIdxEnd; i++)
			{
				if (patArr[i] == '*')
				{
					patIdxTmp = i;
					break;
				}
			}
			if (patIdxTmp == patIdxStart+1)
			{
				// Two stars next to each other, skip the first one.
				patIdxStart++;
				continue;
			}
			// Find the pattern between padIdxStart & padIdxTmp in str between
			// strIdxStart & strIdxEnd
			int patLength = (patIdxTmp-patIdxStart-1);
			int strLength = (strIdxEnd-strIdxStart+1);
			int foundIdx  = -1;
			strLoop:
			for (int i = 0; i <= strLength - patLength; i++)
			{
				for (int j = 0; j < patLength; j++)
				{
					ch = patArr[patIdxStart+j+1];
					if (ch != '?' && ch != strArr[strIdxStart+i+j])
					{
						continue strLoop;
					}
				}

				foundIdx = strIdxStart+i;
				break;
			}

			if (foundIdx == -1)
			{
				return false;
			}

			patIdxStart = patIdxTmp;
			strIdxStart = foundIdx+patLength;
		}

		// All characters in the string are used. Check if only '*'s are left
		// in the pattern. If so, we succeeded. Otherwise failure.
		for (int i = patIdxStart; i <= patIdxEnd; i++)
		{
			if (patArr[i] != '*')
			{
				return false;
			}
		}
		return true;
	}

	// Begin SET and GET methods	

	/**
	 * Sets the <CODE>serverName</CODE> attribute to the given value.
	 * 
	 * @param serverName The name of the server you wish to connect to.
	 * @see #getServerName()
	 */
	public void setServerName(String serverName)
	{
		this.serverName = serverName;
	}

	/**
	 * Gets the <CODE>serverName</CODE> attribute.
	 * 
	 * @return The StarTeam server to log in to.
	 * @see #setServerName(String serverName)
	 */
	public String getServerName()
	{
		return serverName;
	}

	/**
	 * Sets the <CODE>serverPort</CODE> attribute to the given value. The
	 * given value must be a valid integer, but it must be a string object.
	 * 
	 * @param serverPort A string containing the port on the StarTeam server
	 *                   to use.
	 * @see #getServerPort()
	 */
	public void setServerPort(String serverPort)
	{
		this.serverPort = serverPort;
	}

	/**
	 * Gets the <CODE>serverPort</CODE> attribute.
	 * 
	 * @return A string containing the port on the StarTeam server to use.
	 * @see #getServerPortAsInt()
	 * @see #setServerPort(String serverPort)
	 */
	public String getServerPort()
	{
		return serverPort;
	}

	/**
	 * Gets the <CODE>serverPort</CODE> attribute as an integer.
	 * 
	 * @return An integer value for the port on the StarTeam server to use.
	 * @see #getServerPort()
	 * @see #setServerPort(String serverPort)
	 */
	public int getServerPortAsInt()
	{
		return Integer.parseInt(serverPort);
	}

	/**
	 * Sets the <CODE>projectName</CODE> attribute to the given value.
	 * 
	 * @param projectName
	 *               The StarTeam project to search.
	 * @see #getProjectName()
	 */
	public void setProjectName(String projectName)
	{
		this.projectName = projectName;
	}

	/**
	 * Gets the <CODE>projectName</CODE> attribute.
	 * 
	 * @return The StarTeam project to search.
	 * @see #setProjectName(String projectName)
	 */
	public String getProjectName()
	{
		return projectName;
	}

	/**
	 * Sets the <CODE>viewName</CODE> attribute to the given value.
	 * 
	 * @param viewName The view to find the specified folder in.
	 * @see #getViewName()
	 */
	public void setViewName(String viewName)
	{
		this.viewName = viewName;
	}

	/**
	 * Gets the <CODE>viewName</CODE> attribute.
	 * 
	 * @return The view to find the specified folder in.
	 * @see #setViewName(String viewName)
	 */
	public String getViewName()
	{
		return viewName;
	}

	/**
	 * Sets the <CODE>folderName</CODE> attribute to the given value. To
	 * search the root folder, use a slash or backslash, or simply don't set
	 * a folder at all.
	 * 
	 * @param folderName The subfolder from which to check out files.
	 * @see #getFolderName()
	 */
	public void setFolderName(String folderName)
	{
		this.folderName = folderName;
	}

	/**
	 * Gets the <CODE>folderName</CODE> attribute.
	 *
	 * @return The subfolder from which to check out files. All subfolders
	 * will be searched, as well.
	 * @see #setFolderName(String folderName)
	 */
	public String getFolderName()
	{
		return folderName;
	}

	/**
	 * Sets the <CODE>username</CODE> attribute to the given value.
	 * 
	 * @param username Your username for the specified StarTeam server.
	 * @see #getUsername()
	 */
	public void setUsername(String username)
	{
		this.username = username;
	}

	/**
	 * Gets the <CODE>username</CODE> attribute.
	 * 
	 * @return The username given by the user.
	 * @see #setUsername(String username)
	 */
	public String getUsername()
	{
		return username;
	}

	/**
	 * Sets the <CODE>password</CODE> attribute to the given value.
	 * 
	 * @param password Your password for the specified StarTeam server.
	 * @see #getPassword()
	 */
	public void setPassword(String password)
	{
		this.password = password;
	}

	/**
	 * Gets the <CODE>password</CODE> attribute.
	 * 
	 * @return The password given by the user.
	 * @see #setPassword(String password)
	 */
	public String getPassword()
	{
		return password;
	}

	/**
	 * Sets the <CODE>targetFolder</CODE> attribute to the given value.
	 * 
	 * @param target The target path on the local machine to check out to.
	 * @see #getTargetFolder()
	 */
	public void setTargetFolder(String targetFolder)
	{
		this.targetFolder = targetFolder;
	}

	/**
	 * Gets the <CODE>targetFolder</CODE> attribute.
	 * 
	 * @return The target path on the local machine to check out to.
	 *
	 * @see #setTargetFolder(String targetFolder)
	 */
	public String getTargetFolder()
	{
		return targetFolder;
	}

	/**
	 * Sets the <CODE>force</CODE> attribute to the given value.
	 * 
	 * @param force  A string containing "true" or "false" that tells the
	 *               application whether to continue if the target directory
	 *               exists.  If <CODE>force</CODE> is true,
	 *               AntStarTeamCheckOut will overwrite files in the target
	 *               directory.  By default it set to false as a safeguard.
	 *               Note that if the target directory does not exist, this
	 *               setting has no effect.
	 * @see #DEFAULT_FORCESETTING
	 * @see #getForce()
	 * @see #getForceAsBoolean()
	 */
	public void setForce(String force)
	{
		this.force = force;
	}

	/**
	 * Gets the <CODE>force</CODE> attribute.
	 * 
	 * @return A string containing "true" or "false" telling the application
	 *         whether to continue if the target directory exists.  If
	 *         <CODE>force</CODE> is true, AntStarTeamCheckOut will
	 *         overwrite files in the target directory. If it is false and
	 *         the target directory exists, AntStarTeamCheckOut will exit
	 *         with a warning.  If the target directory does not exist, this
	 *         setting has no effect. The default setting is false.
	 * @see #DEFAULT_FORCESETTING
	 * @see #getForceAsBoolean()
	 * @see #setForce(String force)
	 */
	public String getForce()
	{
		return force;
	}

	/**
	 * Gets the <CODE>force</CODE> attribute as a boolean value.
	 * 
	 * @return A boolean value telling whether to continue if the target
	 *         directory exists.
	 * @see #DEFAULT_FORCESETTING
	 * @see #getForce()
	 * @see #setForce(String force)
	 */
	public boolean getForceAsBoolean()
	{
		return project.toBoolean(force);
	}

	/**
	 * Turns recursion on or off.
	 *
	 * @param verbose A string containing "true" or "false."  If it is true,
	 *                the default, subfolders are searched recursively for
	 *                files to check out.  Otherwise, only files specified
	 *                by <CODE>folderName</CODE> are scanned.
	 * @see #DEFAULT_RECURSIONSETTING
	 * @see #getRecursion()
	 * @see #getRecursionAsBoolean()
	 */
	public void setRecursion(String recursion)
	{
		this.recursion = recursion;
	}

	/**
	 * Gets the <CODE>recursion</CODE> attribute, which tells
	 * AntStarTeamCheckOut whether to search subfolders when checking out
	 * files.
	 *
	 * @return A string telling whether <CODE>recursion</CODE> is "true" or
	 * "false."
	 *
	 * @see #DEFAULT_RECURSIONSETTING
	 * @see #getRecursionAsBoolean()
	 * @see #setRecursion(String recursion)
	 */
	public String getRecursion()
	{
		return recursion;
	}

	/**
	 * Gets the <CODE>recursion</CODE> attribute as a boolean value.
	 *
	 * @return A boolean value telling whether subfolders of
	 * <CODE>folderName</CODE> will be scanned for files to check out.
	 *
	 * @see #DEFAULT_RECURSIONSETTING
	 * @see #getRecursion()
	 * @see #setRecursion(String recursion)
	 */
	public boolean getRecursionAsBoolean()
	{
		return project.toBoolean(recursion);
	}

	/**
	 * Sets the <CODE>verbose</CODE> attribute to the given value.
	 * 
	 * @param verbose A string containing "true" or "false" to tell
	 *                AntStarTeamCheckOut whether to display files as they
	 *                are checked out.  By default it is false, so the
	 *                program only displays the total number of files unless
	 *                you override this default.
	 * @see #DEFAULT_FORCESETTING
	 * @see #getForce()
	 * @see #getForceAsBoolean()
	 */
	public void setVerbose(String verbose)
	{
		this.verbose = verbose;
	}

	/**
	 * Gets the <CODE>verbose</CODE> attribute.
	 * 
	 * @return A string containing "true" or "false" telling the application
	 *         to display all files as it checks them out.  By default it is
	 *         false, so the program only displays the total number of
	 *         files.
	 * @see #DEFAULT_VERBOSESETTING
	 * @see #getVerboseAsBoolean()
	 * @see #setVerbose(String verbose)
	 */
	public String getVerbose()
	{
		return verbose;
	}

	/**
	 * Gets the <CODE>verbose</CODE> attribute as a boolean value.
	 * 
	 * @return A boolean value telling whether to display all files as they
	 *         are checked out.
	 * @see #DEFAULT_VERBOSESETTING
	 * @see #getVerbose()
	 * @see #setVerbose(String verbose)
	 */
	public boolean getVerboseAsBoolean()
	{
		return project.toBoolean(verbose);
	}

	// Begin filter getters and setters

	/**
	 * Sets the include filter. When filtering files, AntStarTeamCheckOut
	 * uses an unmodified version of <CODE>DirectoryScanner</CODE>'s
	 * <CODE>match</CODE> method, so here are the patterns straight from the
	 * Ant source code:
	 * <BR><BR>
	 * Matches a string against a pattern. The pattern contains two special
	 * characters:
	 * <BR>'*' which means zero or more characters,
	 * <BR>'?' which means one and only one character.
	 * <BR><BR>
	 * I would have used the Ant method directly from its class, but
	 * <CODE>match</CODE> is a private member, so I cannot access it from
	 * this program.
	 * <BR><BR>
	 * Separate multiple inlcude filters by <I>spaces</I>, not commas as Ant
	 * uses. For example, if you want to check out all .java and .class\
	 * files, you would put the following line in your program:
	 * <CODE>setIncludes("*.java *.class");</CODE>
	 * Finally, note that filters have no effect on the <B>directories</B>
	 * that are scanned; you could not check out files from directories with
	 * names beginning only with "build," for instance. Of course, you
	 * could limit AntStarTeamCheckOut to a particular folder and its
	 * subfolders with the <CODE>setFolderName(String folderName)</CODE>
	 * command.
	 * <BR><BR>
	 * Treatment of overlapping inlcudes and excludes: To give a simplistic
	 * example suppose that you set your include filter to "*.htm *.html"
	 * and your exclude filter to "index.*". What happens to index.html?
	 * AntStarTeamCheckOut will not check out index.html, as it matches an
	 * exclude filter ("index.*"), even though it matches the include
	 * filter, as well.
	 * <BR><BR>
	 * Please also read the following sections before using filters:
	 * 
	 * @param includes A string of filter patterns to include. Separate the
	 *                 patterns by spaces.
	 * @see #getIncludes()
	 * @see #setExcludes(String excludes)
	 * @see #getExcludes()
	 */
	public void setIncludes(String includes)
	{
		this.includes = includes;
	}

	/**
	 * Gets the patterns from the include filter. Rather that duplicate the
	 * details of AntStarTeanCheckOut's filtering here, refer to these
	 * links:
	 * 
	 * @return A string of filter patterns separated by spaces.
	 * @see #setIncludes(String includes)
	 * @see #setExcludes(String excludes)
	 * @see #getExcludes()
	 */
	public String getIncludes()
	{
		return includes;
	}

	/**
	 * Sets the exclude filter. When filtering files, AntStarTeamCheckOut
	 * uses an unmodified version of <CODE>DirectoryScanner</CODE>'s
	 * <CODE>match</CODE> method, so here are the patterns straight from the
	 * Ant source code:
	 * <BR><BR>
	 * Matches a string against a pattern. The pattern contains two special
	 * characters:
	 * <BR>'*' which means zero or more characters,
	 * <BR>'?' which means one and only one character.
	 * <BR><BR>
	 * I would have used the Ant method directly from its class, but
	 * <CODE>match</CODE> is a private member, so I cannot access it from
	 * this program.
	 * <BR><BR>
	 * Separate multiple exlcude filters by <I>spaces</I>, not commas as Ant
	 * uses. For example, if you want to check out all files except .XML and
	 * .HTML files, you would put the following line in your program:
	 * <CODE>setExcludes("*.XML *.HTML");</CODE>
	 * Finally, note that filters have no effect on the <B>directories</B>
	 * that are scanned; you could not skip over all files in directories
	 * whose names begin with "project," for instance.
	 * <BR><BR>
	 * Treatment of overlapping inlcudes and excludes: To give a simplistic
	 * example suppose that you set your include filter to "*.htm *.html"
	 * and your exclude filter to "index.*". What happens to index.html?
	 * AntStarTeamCheckOut will not check out index.html, as it matches an
	 * exclude filter ("index.*"), even though it matches the include
	 * filter, as well.
	 * <BR><BR>
	 * Please also read the following sections before using filters:
	 * 
	 * @param excludes A string of filter patterns to exclude. Separate the
	 *                 patterns by spaces.
	 * @see #setIncludes(String includes)
	 * @see #getIncludes()
	 * @see #getExcludes()
	 */
	public void setExcludes(String excludes)
	{
		this.excludes = excludes;
	}

	/**
	 * Gets the patterns from the exclude filter. Rather that duplicate the
	 * details of AntStarTeanCheckOut's filtering here, refer to these
	 * links:
	 * 
	 * @return A string of filter patterns separated by spaces.
	 * @see #setExcludes(String excludes)
	 * @see #setIncludes(String includes)
	 * @see #getIncludes()
	 */
	public String getExcludes()
	{
		return excludes;
	}

}
